package com.vhall.component.service.interactive.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.base.Splitter;
import com.vhall.component.dao.interactive.AnswerMapper;
import com.vhall.component.dao.interactive.QaQuestionMapper;
import com.vhall.component.dao.interactive.QuestionSameUserMapper;
import com.vhall.component.entity.interactive.ListReqVo;
import com.vhall.component.entity.interactive.dto.AnswerDto;
import com.vhall.component.entity.interactive.dto.CreateDto;
import com.vhall.component.entity.interactive.dto.QuestionDto;
import com.vhall.component.entity.interactive.dto.QuestionListDto;
import com.vhall.component.entity.interactive.entity.AnswerEntity;
import com.vhall.component.entity.interactive.entity.QaSameUserEntity;
import com.vhall.component.entity.interactive.entity.QuestionEntity;
import com.vhall.component.entity.interactive.vo.*;
import com.vhall.component.entity.room.dto.RoomJoinsEntityDTO;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.constant.RedisKey;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.utils.JsonUtil;
import com.vhall.component.plugin.core.service.PaasService;
import com.vhall.component.plugin.core.service.event.PaasMessageEvent;
import com.vhall.component.service.interactive.QaService;
import com.vhall.component.service.interactive.common.QaConsts;
import com.vhall.component.service.room.RoomInnerService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author xianglei
 * @date 2021/7/8 17:20
 */

@Slf4j
@Service
public class QaServiceImpl implements QaService {

    private static final String ROLE_NAME = "role_name";
    private static final String CREATED_AT = "created_at";
    private static final String CONTENT = "content";
    private static final String ROOM_JOIN_ID = "room_join_id";
    private static final String NICK_NAME = "nick_name";
    private static final String AVATAR = "avatar";

    public static final long EXPIRE_SECONDS = 86400 * 30L;

    @Autowired
    private RoomInnerService roomInnerService;
    @Autowired
    private QaQuestionMapper qaQuestionMapper;
    @Autowired
    private PaasService paasService;
    @Autowired
    private AnswerMapper answerMapper;
    @Autowired
    private QuestionSameUserMapper questionSameUserMapper;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    @Value("${paas.apps.lite.appId}")
    public String appId;


    @Autowired
    @Setter
    private ApplicationEventPublisher applicationEventPublisher;


    @Override
    public QuestionRspVo create(CreateReqVo reqVo) {
        RoomJoinsEntityDTO roomJoin = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (roomJoin == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }
        CreateDto createDto = new CreateDto();
        if (StringUtils.isNotBlank(roomJoin.getRoleName())) {
            HashMap<String, String> extMap = new HashMap<>(4);
            extMap.put(ROLE_NAME, roomJoin.getRoleName());
            createDto.setExt(JsonUtil.toJsonString(extMap));
        }
        createDto.setContent(reqVo.getContent());
        createDto.setUserId(Long.valueOf(roomJoin.getAccountId()));
        createDto.setNickName(roomJoin.getNickname());
        createDto.setAvatar(roomJoin.getAvatar());
        createDto.setRoomId(reqVo.getRoomId());
        if (reqVo.getIsShow() == null) {
            createDto.setIsShow(0);
        } else {
            createDto.setIsShow(reqVo.getIsShow());
        }
        //置顶
        offTop(createDto, null);
        //创建
        QuestionEntity question = new QuestionEntity();
        BeanUtils.copyProperties(createDto, question);

        LocalDateTime occurT = LocalDateTime.now();
        question.setAppId(appId);
        question.setCreatedAt(occurT);
        question.setOperateTime(occurT);
        question.setUpdatedAt(occurT);
        qaQuestionMapper.insert(question);

        if (question.getStatus() == null) {
            question.setStatus(0);
        }
        if (question.getCareCount() == null) {
            question.setCareCount(0);
        }
        if (question.getAnswerCount() == null) {
            question.setAnswerCount(0);
        }
        if (question.getSameCount() == null) {
            question.setSameCount(0);
        }

        RoomsEntityDTO activity = roomInnerService.getRoomInfo(reqVo.getRoomId());

        // 发布消息事件，进行异步消息处理
        String channelId = activity.getChannelId();
        String messageBody = getBody(question.getId(), reqVo.getContent(), roomJoin, createDto.getIsShow());
        applicationEventPublisher.publishEvent(new PaasMessageEvent.Builder(this, channelId, messageBody).build());

        QuestionRspVo rspVo = new QuestionRspVo();
        BeanUtils.copyProperties(question, rspVo);
        return rspVo;
    }

    @Override
    public Boolean show(ShowReqVo reqVo) {
        RoomJoinsEntityDTO joinUser = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (joinUser == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }
        if ("2".equals(joinUser.getRoleName())) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }

        QuestionDto questionDto = new QuestionDto();
        //发布问答
        QuestionEntity question = getById(reqVo.getQuesId());

        if (null == question) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
        checkQuestion(question, appId, reqVo.getRoomId(), null);
        question.setIsShow(reqVo.getIsShow());
        qaQuestionMapper.updateById(question);
        //更新缓存
        redisTemplate.delete(buildCacheKey(question.getId()));

        //获取问答信息
        question.setAnswers(getAnswerTree(reqVo.getQuesId()));
        BeanUtils.copyProperties(question, questionDto);
        HashMap<String, String> map;
        String roleName = null;
        if (StringUtils.isNotBlank(questionDto.getExt())) {
            map = JSON.parseObject(questionDto.getExt(), HashMap.class);
            roleName = map.get(ROLE_NAME);
            questionDto.setRoleName(roleName);
        }
        List<AnswerEntity> answers = questionDto.getAnswers();
        for (int i = 0; i < answers.size(); i++) {
            if (StringUtils.isNotBlank(answers.get(i).getExt())) {
                answers.get(i).setRoleName(roleName);
            }
        }
        log.info("question_show_status,data:{}", questionDto);
        JSONObject body = new JSONObject();
        body.put("type", "question_show_status");
        Map<String, Object> params = BeanUtil.beanToMap(questionDto, true, false);
        LocalDateTime operateTime = (LocalDateTime) params.get("operate_time");
        if (operateTime != null) {
            String operateTimeStr = operateTime.format(DatePattern.NORM_DATETIME_FORMATTER);
            params.put("operate_time", operateTimeStr);
        }
        LocalDateTime createdAt = (LocalDateTime) params.get(CREATED_AT);
        if (createdAt != null) {
            String createdStr = createdAt.format(DatePattern.NORM_DATETIME_FORMATTER);
            params.put(CREATED_AT, createdStr);
        }
        LocalDateTime updatedAt = (LocalDateTime) params.get("updated_at");
        if (updatedAt != null) {
            String updatedStr = updatedAt.format(DatePattern.NORM_DATETIME_FORMATTER);
            params.put("updated_at", updatedStr);
        }
        LocalDateTime deletedAt = (LocalDateTime) params.get("deleted_at");
        if (deletedAt != null) {
            String deletedStr = deletedAt.format(DatePattern.NORM_DATETIME_FORMATTER);
            params.put("deleted_at", deletedStr);
        }
        body.put(CONTENT, params);
        String s = body.toJSONString();
        RoomsEntityDTO activity = roomInnerService.getRoomInfo(reqVo.getRoomId());
        try {
            paasService.sendMessage(activity.getChannelId(), s);
        } catch (Exception e) {
            throw new BusinessException(BizErrorCode.BIZ_PAAS_FAILED);
        }
        return true;
    }

    @Override
    public AnswerDto answer(AnswerReqVo reqVo) {
        RoomJoinsEntityDTO roomJoin = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (roomJoin == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }
        //创建回复
        AnswerEntity answer = new AnswerEntity();
        if (StringUtils.isNotBlank(roomJoin.getRoleName())) {
            HashMap<String, String> extMap = new HashMap<>(4);
            extMap.put(ROLE_NAME, roomJoin.getRoleName());
            answer.setExt(JsonUtil.toJsonString(extMap));
        }
        if (Strings.isNotBlank(reqVo.getContent())) {
            answer.setContent(reqVo.getContent());
        } else {
            answer.setContent("");
        }
        answer.setJoinId(Long.valueOf(roomJoin.getAccountId()));
        answer.setRoomId(reqVo.getRoomId());
        answer.setNickName(roomJoin.getNickname());
        answer.setRoleName(roomJoin.getRoleName());
        answer.setAvatar(roomJoin.getAvatar());
        Integer isOpen = reqVo.getIsOpen() == null ? 1 : reqVo.getIsOpen();
        answer.setIsOpen(isOpen);
        Integer type = reqVo.getType() == null ? 0 : reqVo.getType();
        answer.setType(type);
        answer.setQuesId(reqVo.getQuesId());
        QuestionEntity question = getQuestionById(reqVo.getQuesId());
        if (question == null) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
        // 在活动中的角色，0:普通用户， 1：播主，2：管理员. 注意不是用户登陆时的角色
        answer.setRole(0);
        answer.setAppId(appId);
        answer.setCreatedAt(LocalDateTime.now());
        answer.setUpdatedAt(LocalDateTime.now());
        answer.setStatus(0);
        answer.setIsBackout(0);
        answer.setFavoredCount(0);
        answerMapper.insert(answer);
        int status = 3;
        if (!question.getRoomId().equalsIgnoreCase(answer.getRoomId())) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
        if (answer.getType() == 1) {
            //语音回复
            status = 4;
        }
        question.setStatus(status);
        question.setAnswerCount(question.getAnswerCount() + 1);
        qaQuestionMapper.updateById(question);
        redisTemplate.opsForValue().set(RedisKey.QA_KEY_PREFIX + question.getId(), JSON.toJSONString(question));
        sendPaasMessage(answer, reqVo, roomJoin);
        AnswerDto answerDto = new AnswerDto();
        BeanUtils.copyProperties(answer, answerDto);
        return answerDto;
    }

    private void sendPaasMessage(AnswerEntity answer, AnswerReqVo reqVo, RoomJoinsEntityDTO roomJoin) {
        JSONObject body = new JSONObject();
        body.put("type", "question_answer_submit");
        Map<String, Object> content = new HashMap<>(16);
        content.put("id", answer.getId());
        content.put(CONTENT, reqVo.getContent());
        content.put(ROOM_JOIN_ID, roomJoin.getAccountId());
        content.put(NICK_NAME, roomJoin.getNickname());
        content.put(AVATAR, roomJoin.getAvatar());
        content.put(ROLE_NAME, roomJoin.getRoleName());
        content.put("is_open", reqVo.getIsOpen() == null ? 1 : reqVo.getIsOpen());
        content.put("type", reqVo.getType() == null ? 0 : reqVo.getType());
        content.put("ques_id", reqVo.getQuesId());
        content.put(CREATED_AT, LocalDateTimeUtil.formatNormal(LocalDateTime.now()));
        body.put(CONTENT, content);
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVo.getRoomId());
        paasService.sendMessage(roomInfo.getChannelId(), body.toJSONString());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> lists(ListReqVo reqVo) {
        LocalDateTime startTime;
        LocalDateTime endTime = null;
        try {
            if (StringUtils.isBlank(reqVo.getStartTime())) {
                startTime = LocalDateTime.of(2019, 12, 29, 00, 00, 00);
            } else {
                startTime = LocalDateTimeUtil.parse(reqVo.getStartTime(), DatePattern.NORM_DATETIME_FORMATTER);
            }
            if (StringUtils.isNotBlank(reqVo.getEndTime())) {
                endTime = LocalDateTimeUtil.parse(reqVo.getEndTime(), DatePattern.NORM_DATETIME_FORMATTER);
            }
        } catch (Exception e) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_TIME);
        }

        Integer currPage = reqVo.getCurrPage() == null ? 0 : reqVo.getCurrPage();
        Integer pageSize = reqVo.getPageSize() == null ? 20 : reqVo.getPageSize();
        String sort = StringUtils.isNotBlank(reqVo.getSort()) ? reqVo.getSort() : "desc";
        String version = StringUtils.isNotBlank(reqVo.getVersion()) ? reqVo.getVersion() : "v1";
        QuestionListDto listDto = new QuestionListDto();
        listDto.setAppId(appId);
        listDto.setRoomId(reqVo.getRoomId());
        listDto.setStartTime(startTime);
        //v2参数
        listDto.setLimit(pageSize);
        //v1参数
        listDto.setPageSize(pageSize);
        listDto.setSort(sort);
        // 使用向共享服务接口的版本， app 端使用的 v1 版本， 主持端使用的 v2 版本
        listDto.setVersion(version);
        listDto.setCurrPage(currPage);
        listDto.setLastQid(reqVo.getLastQid());
        if (reqVo.getEndTime() != null) {
            listDto.setEndTime(endTime);
        }
        //观众 显示所有已发布及该用户下所有未发布已发布问答
        if (Objects.equals(reqVo.getRole(), 2)) {
            listDto.setShowType(String.valueOf(reqVo.getAccountId()));
            log.info("qa_list_role,data:{}", listDto);
        }
        if (reqVo.getStatus() != null) {
            listDto.setStatus(String.valueOf(reqVo.getStatus()));
        }
        if (reqVo.getIsShow() != null) {
            listDto.setIsShow(reqVo.getIsShow());
        }
        if (listDto.getEndTime() != null && listDto.getStartTime() != null && listDto.getEndTime().
                isBefore(listDto.getStartTime())) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        if (StringUtils.isBlank(listDto.getSortBy())) {
            listDto.setSortBy(CREATED_AT);
        }
        Map<String, Object> data;

        if (Objects.equals(listDto.getVersion(), "v1")) {
            data = listV1ToPub(listDto);
        } else {
            data = listV2ToPub(listDto);
        }
        Set<Long> alsoAskQuestionIds = getSameByUser(appId, reqVo.getRoomId(), Long.valueOf(reqVo.getAccountId()));
        List<QuestionEntity> list = (List<QuestionEntity>) data.get("list");
        list.forEach(r -> {
            // 当前用户对问题是否发起同问
            boolean contains = alsoAskQuestionIds.contains(r.getId());
            if (contains) {
                r.setIsSame(1);
            } else {
                r.setIsSame(0);
            }
        });
        return data;
    }


    @Override
    public Boolean switchs(SwitchReqVo reqVo) {
        redisTemplate.opsForHash().put(RedisKey.INTERACT_TOOL + reqVo.getRoomId(), RedisKey.QA_OPEN, String.valueOf(reqVo.getStatus()));

        RoomJoinsEntityDTO joinUser = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (joinUser == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }
        String nickName = StringUtils.isBlank(joinUser.getNickname()) ? "主办方" : joinUser.getNickname();
        JSONObject body = new JSONObject();
        if (Objects.equals(1, reqVo.getStatus())) {
            //开启问答
            body.put("type", "question_answer_open");
        } else {
            //关闭问答
            body.put("type", "question_answer_close");
        }
        body.put(NICK_NAME, nickName);
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVo.getRoomId());
        try {
            paasService.sendMessage(roomInfo.getChannelId(), body.toJSONString());
        } catch (Exception e) {
            throw new BusinessException(BizErrorCode.BIZ_PAAS_FAILED);
        }
        return true;
    }

    @Override
    public QuestionRspVo deal(DealReqVo reqVo) {
        RoomJoinsEntityDTO joinUser = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (joinUser == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }

        JSONObject body = new JSONObject();
        HashMap<String, Object> map = new HashMap<>(16);
        map.put("operator", joinUser.getNickname());
        Integer status = reqVo.getStatus() == null ? 0 : reqVo.getStatus();
        map.put("status", status);
        map.put("id", reqVo.getId());
        map.put(ROOM_JOIN_ID, joinUser.getAccountId());
        map.put(AVATAR, joinUser.getAvatar());
        map.put(ROLE_NAME, joinUser.getRoleName());
        map.put(CREATED_AT, LocalDateTimeUtil.formatNormal(LocalDateTime.now()));
        body.put("type", "question_answer_deal");
        body.put(CONTENT, map);
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVo.getRoomId());
        try {
            paasService.sendMessage(roomInfo.getChannelId(), body.toJSONString());
        } catch (Exception e) {
            throw new BusinessException(BizErrorCode.BIZ_PAAS_FAILED);
        }

        QuestionEntity question = getById(reqVo.getId());
        if (question == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        checkQuestion(question, appId, reqVo.getRoomId(), null);
        //更新状态
        question.setStatus(reqVo.getStatus());
        question.setOperator(joinUser.getNickname());
        question.setOperateTime(LocalDateTime.now());
        qaQuestionMapper.updateById(question);
        //更新缓存
        redisTemplate.delete(buildCacheKey(question.getId()));
        QuestionRspVo rspVo = new QuestionRspVo();
        BeanUtils.copyProperties(question, rspVo);

        return rspVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AnswerRspVo dealAnswer(DealAnswerReqVo reqVo) {
        RoomJoinsEntityDTO joinUser = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (joinUser == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }

        AnswerEntity answer = getAnswerById(reqVo.getId());
        if (answer == null || !appId.equalsIgnoreCase(answer.getAppId())
                || !reqVo.getRoomId().equalsIgnoreCase(answer.getRoomId())) {
            throw new BusinessException(BizErrorCode.COMP_QA_ANSWER_NOT_FOUND);
        }
        if (reqVo.getIsBackout() != null) {
            answer.setIsBackout(reqVo.getIsBackout());
        }

        answer.setUpdatedAt(LocalDateTime.now());
        answerMapper.updateById(answer);
        //更新缓存
        redisTemplate.opsForValue().set(RedisKey.QA_KEY_ANSWER_PREFIX + answer.getId(), JSON.toJSONString(answer));

        JSONObject body = new JSONObject();
        HashMap<String, Object> map = new HashMap<>(8);
        map.put("operator", joinUser.getNickname());
        Integer isBackOut = reqVo.getIsBackout() == null ? 0 : reqVo.getIsBackout();
        map.put("is_backout", isBackOut);
        map.put("id", reqVo.getId());
        map.put("ques_id", answer.getQuesId());
        body.put("type", "question_answer_deal_answer");
        body.put(CONTENT, map);
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVo.getRoomId());
        try {
            paasService.sendMessage(roomInfo.getChannelId(), body.toJSONString());
        } catch (Exception e) {
            throw new BusinessException(BizErrorCode.BIZ_PAAS_FAILED);
        }
        AnswerRspVo rspVo = new AnswerRspVo();
        BeanUtils.copyProperties(answer, rspVo);
        return rspVo;
    }

    @Override
    public Boolean switchAlsoAsk(SwitchReqVo reqVo) {
        redisTemplate.opsForHash().put(RedisKey.INTERACT_TOOL + reqVo.getRoomId(), RedisKey.QA_ALSO_ASK_OPEN, String.valueOf(reqVo.getStatus()));
        RoomJoinsEntityDTO joinUser = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (joinUser == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }
        String nickName = StringUtils.isBlank(joinUser.getNickname()) ? "主办方" : joinUser.getNickname();
        JSONObject body = new JSONObject();
        if (Objects.equals(1, reqVo.getStatus())) {
            //开启问答组件同问功能
            body.put("type", "qa_also_ask_open");
        } else {
            //关闭问答组件同问功能
            body.put("type", "qa_also_ask_close");
        }
        body.put(NICK_NAME, nickName);
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVo.getRoomId());
        try {
            paasService.sendMessage(roomInfo.getChannelId(), body.toJSONString());
        } catch (Exception e) {
            throw new BusinessException(BizErrorCode.BIZ_PAAS_FAILED);
        }
        return true;
    }

    @Override
    public QuestionRspVo toggleAlsoAsk(ToggleAlsoAskReqVo reqVo) {
        RoomJoinsEntityDTO joinUser = roomInnerService.getRoomJoinsByRoomIdAndThirdId(reqVo.getRoomId(), reqVo.getAccountId());
        if (joinUser == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_USER);
        }
        QuestionEntity question = getById(reqVo.getQuesId());

        if (question == null || !appId.equals(question.getAppId())) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
        if (StringUtils.isNotBlank(reqVo.getRoomId()) && !reqVo.getRoomId().equals(question.getRoomId())) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
        Boolean save = saveQuestionSaveUser(reqVo);
        if (save) {
            if (QaConsts.QUESTION_SAME_FALSE == reqVo.getType()) {
                //取消同问数
                if (question.getSameCount() > 0) {
                    while (true) {
                        int row = qaQuestionMapper.decrementSameCount(reqVo.getQuesId(), question.getSameCount());
                        if (row > 0) {
                            break;
                        }
                        question = get4UpdateById(reqVo.getQuesId());
                    }

                }
            } else {
                while (true) {
                    //增加同问数
                    int row = qaQuestionMapper.incrementSameCount(reqVo.getQuesId(), question.getSameCount());
                    if (row > 0) {
                        break;
                    }
                    question = get4UpdateById(reqVo.getQuesId());
                }

            }
            //更新缓存
            redisTemplate.delete(buildCacheKey(question.getId()));
            // 重新操作了DB, 需要缓存刷新
            question = getById(reqVo.getQuesId());
            if (null == question) {
                throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
            }
        }

        JSONObject body = new JSONObject();
        HashMap<String, Object> map = new HashMap<>(16);
        map.put("id", question.getId());
        map.put("user_id", joinUser.getAccountId());
        map.put("type", reqVo.getType());
        map.put(CONTENT, question.getContent());
        Integer isOpen = question.getIsOpen() == null ? 1 : question.getIsOpen();
        map.put("is_open", isOpen);
        Integer sameCount = question.getSameCount() == null ? 0 : question.getSameCount();
        map.put("same_count", sameCount);
        map.put(CREATED_AT, LocalDateTimeUtil.formatNormal(LocalDateTime.now()));
        body.put("type", "qa_also_ask_count");
        body.put(CONTENT, map);
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVo.getRoomId());
        try {
            paasService.sendMessage(roomInfo.getChannelId(), body.toJSONString());
        } catch (Exception e) {
            throw new BusinessException(BizErrorCode.BIZ_PAAS_FAILED);
        }
        QuestionRspVo rspVo = new QuestionRspVo();
        BeanUtils.copyProperties(question, rspVo);
        return rspVo;
    }

    private String getBody(Long id, String content, RoomJoinsEntityDTO dto, Integer isShow) {
        JSONObject param = new JSONObject();
        HashMap<String, Object> map = new HashMap<>(16);
        map.put("id", id);
        map.put(CONTENT, content);
        map.put(ROOM_JOIN_ID, dto.getAccountId());
        map.put(NICK_NAME, dto.getNickname());
        map.put(AVATAR, dto.getAvatar());
        map.put(ROLE_NAME, dto.getRoleName());
        map.put("is_show", isShow);
        map.put(CREATED_AT, LocalDateTimeUtil.formatNormal(LocalDateTime.now()));

        param.put("type", "question_answer_create");
        param.put(CONTENT, map);
        return param.toJSONString();
    }

    private QuestionEntity get4UpdateById(Long id) {
        QuestionEntity q = qaQuestionMapper.selectByIdForUpdate(id);
        redisTemplate.opsForValue().set(buildCacheKey(id), JSONObject.toJSONString(q), EXPIRE_SECONDS, TimeUnit.SECONDS);
        return q;
    }


    /**
     * 如果是置顶的，将同活动下其余置顶提问置为非置顶
     *
     * @param reqVo
     */
    private void offTop(CreateDto reqVo, Long id) {
        QuestionEntity question = new QuestionEntity();
        if (reqVo.getTop() != null && QaConsts.QUESTION_TOP == reqVo.getTop()) {
            question.setTop(QaConsts.QUESTION_NOT_TOP);
            qaQuestionMapper.update(question, Wrappers.<QuestionEntity>lambdaQuery()
                    .eq(QuestionEntity::getRoomId, reqVo.getRoomId())
                    .eq(id != null, QuestionEntity::getId, id));
        }
    }

    private Boolean saveQuestionSaveUser(ToggleAlsoAskReqVo reqVo) {
        QaSameUserEntity sameUser = questionSameUserMapper.selectOne(Wrappers.<QaSameUserEntity>lambdaQuery()
                .eq(QaSameUserEntity::getQuestionId, reqVo.getQuesId())
                .eq(QaSameUserEntity::getUserId, reqVo.getAccountId()).last("limit 0, 1"));
        if (sameUser != null) {
            if (!sameUser.getStatus().equals(reqVo.getType())) {
                sameUser.setStatus(reqVo.getType());
                sameUser.setUpdatedAt(LocalDateTime.now());
                questionSameUserMapper.updateById(sameUser);
            } else {
                return false;
            }
        } else {
            sameUser = new QaSameUserEntity();
            sameUser.setQuestionId(reqVo.getQuesId());
            sameUser.setUserId(Long.valueOf(reqVo.getAccountId()));
            sameUser.setStatus(reqVo.getType());
            sameUser.setCreatedAt(LocalDateTime.now());
            questionSameUserMapper.insert(sameUser);
        }
        String cacheKey = buildSameUserCacheKey(appId, reqVo.getRoomId(), Long.valueOf(reqVo.getAccountId()));
        if (reqVo.getType() == QaConsts.QUESTION_SAME_TRUE) {
            redisTemplate.opsForSet().add(cacheKey, reqVo.getQuesId().toString());
        } else if (reqVo.getType() == QaConsts.QUESTION_SAME_FALSE) {
            redisTemplate.opsForSet().remove(cacheKey, reqVo.getQuesId().toString());
        }
        return true;
    }

    private String buildSameUserCacheKey(String appId, String roomId, Long userId) {
        return RedisKey.QA_KEY_ROOM_U_REL + appId + ":" + roomId + ":" + userId;
    }

    private QuestionEntity getById(Long id) {

        String cache = redisTemplate.opsForValue().get(buildCacheKey(id));
        if (StringUtils.isNotEmpty(cache)) {
            return JSON.parseObject(cache, QuestionEntity.class);
        }
        QuestionEntity q = qaQuestionMapper.selectById(id);
        redisTemplate.opsForValue().set(buildCacheKey(id), JsonUtil.toJsonString(q), EXPIRE_SECONDS, TimeUnit.SECONDS);
        return q;
    }

    private String buildCacheKey(Long id) {
        return RedisKey.QA_KEY_PREFIX + id;
    }

    private void checkQuestion(QuestionEntity question, String appId, String roomId, Long userId) {
        if (StringUtils.isNotBlank(appId) && !appId.equals(question.getAppId())) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
        if (StringUtils.isNotBlank(roomId) && !roomId.equals(question.getRoomId())) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
        if (userId != null && !userId.equals(question.getUserId())) {
            throw new BusinessException(BizErrorCode.COMP_QA_NOT_FOUND);
        }
    }

    public List<AnswerEntity> getAnswerTree(Long questionId) {
        List<AnswerEntity> answers = answerMapper.selectList(Wrappers.<AnswerEntity>lambdaQuery()
                .eq(AnswerEntity::getQuesId, questionId)
                .orderByAsc(AnswerEntity::getId));
        Map<Long, AnswerEntity> idMap = answers.stream().collect(Collectors.toMap(AnswerEntity::getId, answer -> answer));
        for (AnswerEntity answer : answers) {
            AnswerEntity parent = idMap.get(answer.getParentId());
            if (parent != null) {
                parent.getChildren().add(answer);
            }
        }
        return idMap.values().stream().sorted(Comparator.comparing(AnswerEntity::getId)).filter(answer -> answer.getParentId() == 0).collect(Collectors.toList());
    }

    /**
     * 根据问题ID查询问题
     */
    private QuestionEntity getQuestionById(Long questId) {
        QuestionEntity question;
        try {
            //查缓存中的问题
            String cache = redisTemplate.opsForValue().get(RedisKey.QA_KEY_PREFIX + questId);
            if (StringUtils.isEmpty(cache)) {
                //查DB中的问题
                QuestionEntity one = qaQuestionMapper.selectOne(Wrappers.<QuestionEntity>lambdaQuery()
                        .eq(QuestionEntity::getId, questId)
                        .isNull(QuestionEntity::getDeletedAt).last("limit 0, 1"));
                if (one != null) {
                    //设置缓存
                    redisTemplate.opsForValue().set(RedisKey.QA_KEY_PREFIX + one.getId(), JSON.toJSONString(one));
                    return one;
                }
                return null;
            }
            question = JSON.parseObject(cache, QuestionEntity.class);
            return question;
        } catch (Exception e) {
            log.error("get_question_by_id_error:", e);
            return null;
        }
    }


    private Map<String, Object> listV1ToPub(QuestionListDto dto) {
        if (dto.getUserId() != null) {
            dto.setIsShow(null);
        }
        LambdaQueryWrapper<QuestionEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.like(StringUtils.isNotBlank(dto.getContent()), QuestionEntity::getContent, dto.getContent())
                .eq(dto.getId() != null, QuestionEntity::getId, dto.getId())
                .eq(StringUtils.isNotBlank(dto.getRoomId()), QuestionEntity::getRoomId, dto.getRoomId())
                .eq(dto.getCareCount() != null, QuestionEntity::getCareCount, dto.getCareCount())
                .eq(dto.getAnswerCount() != null, QuestionEntity::getAnswerCount, dto.getAnswerCount())
                .eq(dto.getIsQuality() != null, QuestionEntity::getIsQuality, dto.getIsQuality())
                .like(StringUtils.isNotBlank(dto.getOperator()), QuestionEntity::getOperator, dto.getOperator())
                .like(dto.getNickName() != null, QuestionEntity::getNickName, dto.getNickName());
        //status 逗号分隔处理
        if (StringUtils.isNotBlank(dto.getStatus())) {
            try {
                List<Integer> statusList = Splitter.on(",").splitToList(dto.getStatus()).parallelStream().map(s -> {
                    if (StringUtils.isNotBlank(s)) {
                        return Integer.parseInt(s.trim());
                    }
                    return null;
                }).distinct().collect(Collectors.toList());
                wrapper.in(statusList.size() > 0, QuestionEntity::getStatus, statusList);
            } catch (Exception ex) {
                throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
            }
        }
        wrapper.eq(dto.getIsShow() != null, QuestionEntity::getIsShow, dto.getIsShow())
                .eq(dto.getIsOpen() != null, QuestionEntity::getIsOpen, dto.getIsOpen())
                .eq(dto.getTop() != null, QuestionEntity::getTop, dto.getTop())
                .eq(StringUtils.isNotBlank(dto.getAppId()), QuestionEntity::getAppId, dto.getAppId());
        //showType传递用户ID，显示所有已发布及该用户下所有未发布已发布问答，有此值时user_id参数不生效
        if (StringUtils.isNotBlank(dto.getShowType())) {
            wrapper.and(andWrapper -> andWrapper.eq(QuestionEntity::getIsShow, QaConsts.IS_SHOW_VALUE_ZERO)
                    .or().eq(QuestionEntity::getUserId, dto.getShowType()));
        }
        //openType传递用户ID，显示所有公开及该用户下所有私密、公开问答，有此值时user_id参数不生效
        if (StringUtils.isNotBlank(dto.getOpenType())) {
            wrapper.and(andWrapper -> andWrapper.eq(QuestionEntity::getIsOpen, QaConsts.IS_OPEN_VALUE_ZERO)
                    .or().eq(QuestionEntity::getUserId, dto.getOpenType()));
        }
        wrapper.eq(dto.getUserId() != null, QuestionEntity::getUserId, dto.getUserId());
        //ids 逗号分隔处理
        if (StringUtils.isNotBlank(dto.getIds())) {
            try {
                List<Integer> idList = Splitter.on(",").splitToList(dto.getIds()).parallelStream().map(s -> {
                    if (StringUtils.isNotBlank(s)) {
                        return Integer.parseInt(s.trim());
                    }
                    return null;
                }).distinct().collect(Collectors.toList());
                wrapper.in(idList.size() > 0, QuestionEntity::getId, idList);
            } catch (Exception ex) {
                throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
            }
        }
        //创建时间 查询区间
        wrapper.ge(dto.getStartTime() != null, QuestionEntity::getCreatedAt, dto.getStartTime())
                .le(dto.getEndTime() != null, QuestionEntity::getCreatedAt, dto.getEndTime());
        Integer count = qaQuestionMapper.selectCount(wrapper);
        Map<String, Object> result = new HashMap<>(8);
        int currPage = dto.getCurrPage() <= 0 ? 1 : dto.getCurrPage();
        Integer offset = (currPage - 1) * dto.getPageSize();
        //排序
        wrapper.last("order by top " + QaConsts.SORT_DESC
                + ", " + dto.getSortBy() + " " + getSort(dto.getSort())
                + ", id " + QaConsts.SORT_DESC + " limit " + offset + "," + dto.getPageSize());
        List<QuestionEntity> questions = qaQuestionMapper.selectList(wrapper);
        result.put("list", questions);
        int totalPage = count % dto.getPageSize() == 0 ? count / dto.getPageSize() : (count / dto.getPageSize()) + 1;
        result.put("total_page", totalPage);
        result.put("page", currPage);
        result.put("total", count);
        result.put("page_size", dto.getPageSize());
        return result;
    }

    private String getSort(String sort) {
        boolean isAsc = false;
        if (StringUtils.isNotBlank(sort) && QaConsts.SORT_ASC.equalsIgnoreCase(sort.trim())) {
            isAsc = true;
        }
        return isAsc ? QaConsts.SORT_ASC : QaConsts.SORT_DESC;
    }

    private Map<String, Object> listV2ToPub(QuestionListDto dto) {
        if (dto.getUserId() != null) {
            dto.setIsShow(null);
        }
        LambdaQueryWrapper<QuestionEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.like(StringUtils.isNotBlank(dto.getContent()), QuestionEntity::getContent, dto.getContent())
                .eq(dto.getId() != null, QuestionEntity::getId, dto.getId())
                .eq(dto.getCareCount() != null, QuestionEntity::getCareCount, dto.getCareCount())
                .eq(dto.getAnswerCount() != null, QuestionEntity::getAnswerCount, dto.getAnswerCount())
                .like(dto.getNickName() != null, QuestionEntity::getNickName, dto.getNickName())
                .like(StringUtils.isNotBlank(dto.getOperator()), QuestionEntity::getOperator, dto.getOperator());

        // status 逗号分隔处理
        if (StringUtils.isNotBlank(dto.getStatus())) {
            List<Integer> statusList = Splitter.on(",").splitToList(dto.getStatus()).parallelStream().map(s -> {
                if (StringUtils.isNotBlank(s)) {
                    return Integer.parseInt(s.trim());
                }
                return null;
            }).distinct().collect(Collectors.toList());
            wrapper.in(statusList.size() > 0, QuestionEntity::getStatus, statusList);
        }
        wrapper.eq(dto.getIsShow() != null, QuestionEntity::getIsShow, dto.getIsShow())
                .eq(dto.getIsOpen() != null, QuestionEntity::getIsOpen, dto.getIsOpen())
                .eq(dto.getTop() != null, QuestionEntity::getTop, dto.getTop())
                .eq(StringUtils.isNotBlank(dto.getAppId()), QuestionEntity::getAppId, dto.getAppId())
                .eq(StringUtils.isNotBlank(dto.getRoomId()), QuestionEntity::getRoomId, dto.getRoomId());

        // showType传递用户ID，显示所有已发布及该用户下所有未发布已发布问答，有此值时user_id参数不生效
        if (StringUtils.isNotBlank(dto.getShowType())) {
            wrapper.and(andWrapper -> andWrapper.eq(QuestionEntity::getIsShow, QaConsts.IS_SHOW_VALUE_ZERO)
                    .or().eq(QuestionEntity::getUserId, dto.getShowType()));
        }
        // openType传递用户ID，显示所有公开及该用户下所有私密、公开问答，有此值时user_id参数不生效
        if (StringUtils.isNotBlank(dto.getOpenType())) {
            wrapper.and(andWrapper -> andWrapper.eq(QuestionEntity::getIsOpen, QaConsts.IS_OPEN_VALUE_ZERO)
                    .or().eq(QuestionEntity::getUserId, dto.getOpenType()));
        }
        wrapper.eq(dto.getUserId() != null, QuestionEntity::getUserId, dto.getUserId());
        // ids 逗号分隔处理
        if (StringUtils.isNotBlank(dto.getIds())) {
            List<Integer> idList = Splitter.on(",").splitToList(dto.getIds()).parallelStream().map(s -> {
                if (StringUtils.isNotBlank(s)) {
                    return Integer.parseInt(s.trim());
                }
                return null;
            }).distinct().collect(Collectors.toList());
            wrapper.in(idList.size() > 0, QuestionEntity::getId, idList);
        }

        // 创建时间 查询区间
        wrapper.ge(dto.getStartTime() != null, QuestionEntity::getCreatedAt, dto.getStartTime())
                .le(dto.getEndTime() != null, QuestionEntity::getCreatedAt, dto.getEndTime());
        qaQuestionMapper.selectCount(wrapper);
        // 滚动分页
        if (null != dto.getLastQid()) {
            if (dto.getSort().equals(QaConsts.SORT_DESC)) {
                wrapper.lt(QuestionEntity::getId, dto.getLastQid());
            }
            if (dto.getSort().equals(QaConsts.SORT_ASC)) {
                wrapper.gt(QuestionEntity::getId, dto.getLastQid());
            }
        }

        int total = qaQuestionMapper.selectCount(wrapper);

        //排序
        if (!"same_count".equals(dto.getSortBy())) {
            wrapper.last("order by top " + QaConsts.SORT_DESC
                    + ", " + dto.getSortBy() + " " + getSort(dto.getSort())
                    + ", id " + QaConsts.SORT_DESC + " limit " + dto.getLimit());
        } else {
            wrapper.last("order by top desc, id " + dto.getSort() + " limit " + dto.getLimit());
        }

        List<QuestionEntity> qlist = qaQuestionMapper.selectList(wrapper);
        qlist.forEach(question -> question.setAnswers(getAnswerTree(question.getId(), question.getAppId())));

        Map<String, Object> result = new HashMap<>(4);
        result.put("total", total);
        result.put("list", qlist);

        return result;
    }


    public List<AnswerEntity> getAnswerTree(Long questionId, String appId) {
        List<AnswerEntity> answers = answerMapper.selectList(Wrappers.<AnswerEntity>lambdaQuery()
                .eq(AnswerEntity::getQuesId, questionId)
                .eq(StringUtils.isNotBlank(appId), AnswerEntity::getAppId, appId)
                .orderByAsc(AnswerEntity::getId));
        Map<Long, AnswerEntity> idMap = answers.stream().collect(Collectors.toMap(AnswerEntity::getId, answer -> answer));
        for (AnswerEntity answer : answers) {
            AnswerEntity parent = idMap.get(answer.getParentId());
            if (parent != null) {
                parent.getChildren().add(answer);
            }
        }
        return idMap.values().stream().sorted(Comparator.comparing(AnswerEntity::getId)).filter(answer -> answer.getParentId() == 0).collect(Collectors.toList());
    }

    private AnswerEntity getAnswerById(Long answerId) {
        AnswerEntity answer;
        try {
            //查缓存中的问答回复
            String cache = redisTemplate.opsForValue().get(RedisKey.QA_KEY_ANSWER_PREFIX + answerId);
            if (!StringUtils.isEmpty(cache)) {
                answer = JSON.parseObject(cache, AnswerEntity.class);
                return answer;
            }
            //查DB中的问题回复
            AnswerEntity queryAnswer = answerMapper.selectOne(Wrappers.<AnswerEntity>lambdaQuery()
                    .eq(AnswerEntity::getId, answerId)
                    .isNull(AnswerEntity::getDeletedAt).last("limit 0, 1"));
            if (queryAnswer != null) {
                //设置缓存
                redisTemplate.opsForValue().set(RedisKey.QA_KEY_ANSWER_PREFIX + queryAnswer.getId(), JSON.toJSONString(queryAnswer));
                return queryAnswer;
            }
            return null;
        } catch (Exception e) {
            log.error("get_answer_by_id_error:", e);
            return null;
        }

    }

    /**
     * 获取用户同问状态
     *
     * @param appId
     * @param roomId
     * @param userId
     * @return
     */
    public Set<Long> getSameByUser(String appId, String roomId, Long userId) {
        String cacheKey = buildSameUserCacheKey(appId, roomId, userId);
        Set<String> cacheVal = redisTemplate.opsForSet().members(cacheKey);

        Set<Long> set = new HashSet<>();
        if (cacheVal == null) {
            return set;
        }
        for (String item : cacheVal) {
            set.add(Long.parseLong(item));
        }
        return set;
    }
}
